package com.dbflow5.adapter;

import com.dbflow5.database.DatabaseWrapper;
import com.dbflow5.database.FlowCursor;
import com.dbflow5.query.cache.MultiKeyCacheConverter;
import com.dbflow5.query.cache.ModelCache;
import com.dbflow5.structure.InvalidDBConfiguration;

import java.util.Collection;

/**
 * Description:
 */
public abstract class CacheAdapter<T>{

    public ModelCache<T, ?> modelCache;
    public int cachingColumnSize = 1;
    public MultiKeyCacheConverter<?> cacheConverter = null;

    public CacheAdapter(ModelCache<T, ?> modelCache, int cachingColumnSize, MultiKeyCacheConverter<?> cacheConverter){
        this.modelCache = modelCache;
        this.cachingColumnSize = cachingColumnSize;
        this.cacheConverter = cacheConverter;
    }

    /**
     * The cursor to load caching id from.
     *
     * @param cursor The cursor to load caching id from.
     * @return The single cache column from cursor (if single).
     */
    public abstract Object getCachingColumnValueFromCursor(FlowCursor cursor);

    /**
     * The model to load cache column data from.
     *
     * @param model The model to load cache column data from.
     * @return The single cache column from model (if single).
     */
    public abstract Object getCachingColumnValueFromModel(T model);

    /**
     * Loads all primary keys from the [FlowCursor] into the inValues. The size of the array must
     * match all primary keys. This method gets generated when caching is enabled.
     *
     * @param inValues The reusable array of values to populate.
     * @param cursor   The cursor to load from.
     * @return The populated set of values to load from cache.
     */
    public abstract Object[] getCachingColumnValuesFromCursor(Object[] inValues, FlowCursor cursor);

    /**
     * Loads all primary keys from the [TModel] into the inValues. The size of the array must
     * match all primary keys. This method gets generated when caching is enabled. It converts the primary fields
     * of the [TModel] into the array of values the caching mechanism uses.
     *
     * @param inValues The reusable array of values to populate.
     * @param TModel   The model to load from.
     * @return The populated set of values to load from cache.
     */
    public abstract Object[] getCachingColumnValuesFromModel(Object[] inValues, T TModel);

    public void storeModelInCache(T model) {
        modelCache.addModel(getCachingId(model), model);
    }

    public void storeModelsInCache(Collection<T> models) {
        for(T t: models){
            storeModelInCache(t);
        }
    }

    public void removeModelFromCache(T model) {
        Object obj = getCachingId(model);
        modelCache.removeModel(obj);
    }

    public void removeModelsFromCache(Collection<T> models) {
        for(T t : models){
            removeModelFromCache(t);
        }
    }

    public void clearCache() {
        modelCache.clear();
    }

    public Object getCachingId(Object[] inValues) {
        if(inValues != null){
            if(inValues.length == 1){
                return inValues[0];
            }
            if(cacheConverter.getCachingKey(inValues) != null){
                return cacheConverter.getCachingKey(inValues);
            }else {
                throw new InvalidDBConfiguration("For multiple primary keys, a public static MultiKeyCacheConverter field must" +
                        "be  marked with @MultiCacheField in the corresponding model class. The resulting key" +
                        "must be a unique combination of the multiple keys, otherwise inconsistencies may occur.");
            }
        }
        return null;
    }

    public Object getCachingId(T model){
        return getCachingId(getCachingColumnValuesFromModel(new Object[cachingColumnSize], model));
    }

    /**
     * Reloads relationships when loading from [FlowCursor] in a model that's cacheable. By having
     * relationships with cached models, the retrieval will be very fast.
     *
     * @param model model
     * @param cursor The cursor to reload from.
     * @param databaseWrapper databaseWrapper
     */
    public abstract void reloadRelationships(T model, FlowCursor cursor, DatabaseWrapper databaseWrapper);

}